package ru.aslanov.schedule.server;

import com.google.appengine.api.users.UserService;
import com.google.appengine.api.users.UserServiceFactory;
import ru.aslanov.schedule.model.*;
import ru.aslanov.schedule.utils.MemCacheUtil;

import javax.jdo.PersistenceManager;
import java.io.Serializable;
import java.util.*;
import java.util.logging.Logger;

/**
 * Created by IntelliJ IDEA.
 * Created: Feb 8, 2010 5:17:18 PM
 *
 * @author Sergey Aslanov
 */
public class AccessManager implements Serializable {
    private static final Logger log = Logger.getLogger(AccessManager.class.getName());
    private static final UserService userService = UserServiceFactory.getUserService();

    private Map<String, ScheduleUserInfo> scheduleUserInfoMap = Collections.synchronizedMap(new HashMap<String, ScheduleUserInfo>());

    public static AccessManager getInstance() {
        final String cacheKey = getCacheKey();
        AccessManager accessManager = (AccessManager) MemCacheUtil.getObjectFromCache(cacheKey);
        if (accessManager == null) {
            accessManager = new AccessManager();
            MemCacheUtil.cacheObject(cacheKey, accessManager);
        }
        return accessManager;
    }

    public static void removeInstance() {
        MemCacheUtil.removeObjectFromCache(getCacheKey());
    }

    private static String getCacheKey() {
        return "AccessManager" + userService.getCurrentUser().getEmail().toLowerCase();
    }

    private AccessManager() {
    }

    public boolean isAdmin() {
        return userService.isUserAdmin();
    }

    public ScheduleUserInfo getScheduleUserInfo(String scheduleKey) throws AccessDeniedException {
        if (scheduleUserInfoMap == null) {
            scheduleUserInfoMap = new HashMap<String, ScheduleUserInfo>();
        }
        ScheduleUserInfo scheduleUserInfo = scheduleUserInfoMap.get(scheduleKey);
        if (scheduleUserInfo == null) {
            final PersistenceManager pm = PMF.getThreadLocalPersistenceManager();
            //try {
                Collection res = (Collection) pm.newQuery(User.class, "email == :email && schedule == :s").execute(getEmail(), scheduleKey);
                User user = res.size() > 0 ? (User) res.iterator().next() : null;

                res = (Collection) pm.newQuery(Teacher.class, "schedule == :s && email == :e").execute(scheduleKey, getEmail());
                Teacher teacher = res.size() > 0 ? (Teacher) res.iterator().next() : null;

                if (!isAdmin() && user == null && teacher == null) {
                    throw new AccessDeniedException(getEmail());
                }

                scheduleUserInfo = new ScheduleUserInfo(
                        user != null, isAdmin() || (user != null && user.isAdmin()),
                        teacher != null ? teacher.getEncodedKey() : null);

            //} finally {
                //pm.close();
            //}
            scheduleUserInfoMap.put(scheduleKey, scheduleUserInfo);
        }

        return scheduleUserInfo;
    }

    public void checkScheduleAdmin(String scheduleKey) throws AccessDeniedException {
        if (!isScheduleAdmin(scheduleKey)) {
            throw new AccessDeniedException(getEmail());
        }
    }

    public void checkAdmin() throws AccessDeniedException {
        if (!isAdmin()) {
            throw new AccessDeniedException(getEmail());
        }
    }

    public boolean isScheduleAdmin(String scheduleKey) throws AccessDeniedException {
        return isAdmin() || getScheduleUserInfo(scheduleKey).isScheduleAdmin;
    }

    public boolean hasAccessToGroup(String groupKey, Operation operation) throws AccessDeniedException {
        if (isAdmin()) {
            return true;
        } else {
            final PersistenceManager pm = PMF.getThreadLocalPersistenceManager();
            //try {
                final Group group = pm.getObjectById(Group.class, groupKey);
                final String scheduleKey = group.getSchedule().getEncodedKey();

                final ScheduleUserInfo scheduleUserInfo = getScheduleUserInfo(scheduleKey);
                final String teacherKey = scheduleUserInfo.teacherKey;
                if (scheduleUserInfo.isScheduleAdmin) {
                    return true;
                } else if (teacherKey != null && (teacherKey.equals(group.getTeacher1Key()) || teacherKey.equals(group.getTeacher2Key()))) {
                    return true;
                } else if (teacherKey != null || scheduleUserInfo.isUser) {
                    return operation == Operation.VIEW;
                } else {
                    return false;
                }
            //} finally {
                //pm.close();
            //}
        }
    }

    public boolean hasAccessToSchedule(String scheduleKey, Operation operation) throws AccessDeniedException {
        if (isAdmin()) {
            return true;
        } else {
            final ScheduleUserInfo scheduleUserInfo = getScheduleUserInfo(scheduleKey);
            if (scheduleUserInfo.isScheduleAdmin) {
                return operation != Operation.CREATE;
            } else {
                return operation == Operation.VIEW;
            }
        }
    }

    public Collection getUserSchedules(PersistenceManager pm) {
        Collection res;
        if (isAdmin()) {
            res = (Collection) pm.newQuery(Schedule.class).execute();
        } else {
            final String email = getEmail();
            res = new HashSet();

            Collection teachers = (Collection) pm.newQuery(Teacher.class, "email == :email").execute(email);
            for (Object teacher : teachers) {
                res.add(((Teacher) teacher).getSchedule());
            }

            Collection users = (Collection) pm.newQuery(User.class, "email == :email").execute(email);
            for (Object user : users) {
                res.add(((User) user).getSchedule());
            }
        }

        return res;
    }

    public String getEmail() {
        return userService.getCurrentUser().getEmail().toLowerCase();
    }

    public String getLogoutUrl(String nextUrl) {
        return userService.createLogoutURL(nextUrl);
    }

    public String getLoginUrl(String nextUrl) {
        return userService.createLoginURL(nextUrl);
    }

    public String getScheduleTeacherKey(String scheduleKey) throws AccessDeniedException {
        return getScheduleUserInfo(scheduleKey).teacherKey;
    }

    public static class ScheduleUserInfo implements Serializable {
        boolean isScheduleAdmin;
        boolean isUser;
        String teacherKey;

        private ScheduleUserInfo(boolean user, boolean scheduleAdmin, String teacherKey) {
            isScheduleAdmin = scheduleAdmin;
            isUser = user;
            this.teacherKey = teacherKey;
        }

        public boolean isScheduleAdmin() {
            return isScheduleAdmin;
        }

        public boolean isTeacher() {
            return teacherKey != null;
        }

        public boolean isUser() {
            return isUser;
        }

        public String getTeacherKey() {
            return teacherKey;
        }
    }
}
